#include <list.h>
#include "config.h"

#include "lsv_wbuffer_internal.h"

/**
 * 1 init hash table for manage wbuffer
 * */
int lsv_wb_hash_index_init_v2(lsv_volume_proto_t *lsv_info){
        int ret = 0;
        index_block_head *wb_hash_index = NULL;

        wb_hash_index = (index_block_head *) malloc(sizeof(index_block_head) * WBUF_INDEX_SIZE);
        if(NULL == wb_hash_index){
                DERROR("malloc hash index failed.\n");
                ret = ENOMEM;
                GOTO(err_ret, ret);
        }
        memset(wb_hash_index, 0, sizeof(index_block_head) * WBUF_INDEX_SIZE);

        for(int i = 0; i < WBUF_INDEX_SIZE; i++){
                INIT_LIST_HEAD(&wb_hash_index[i].list);
        }

        lsv_wbuf_t *wbuf = lsv_info->wbuf;
        wbuf->wb_hash_index = wb_hash_index;
        lsv_rwlock_init(&wbuf->wb_hash_rwlock);

        return 0;
err_ret:
        if (ret > 0) {
                ret = -ret;
        }
        return ret;
}

int lsv_wb_hash_index_destroy_v2(lsv_volume_proto_t *lsv_info){
        lsv_wbuf_t * wbuf = lsv_info->wbuf;

        if (wbuf->wb_hash_index != NULL) {
                yfree((void **)&wbuf->wb_hash_index);
        }

        return 0;
}

int lsv_wb_hash_index_insert_v2(lsv_volume_proto_t *lsv_info, uint64_t lba, lsv_wbuf_chunk_t *chunk,
                                lsv_u32_t chunk_page_idx){
        int ret = 0;
        lsv_wbuf_t * wbuf = NULL;
        index_block_head * wb_hash_index = NULL;
        chunk_page_t *page;
        lsv_s32_t hash_key = HASH_KEY(lba);

        YASSERT(lba % LSV_PAGE_SIZE == 0);

        page = &chunk->pages[chunk_page_idx];

        page->lba = lba;
        page->chunk = chunk;
        page->page_idx = chunk_page_idx;

        // 判断page不属于任何list
        if (page->hook.next != NULL) {
                YASSERT(page->hook.next == &page->hook);
        }

        if (page->hook.prev != NULL) {
                YASSERT(page->hook.prev == &page->hook);
        }

        // 插入头部，保证最新的出现在头部；查找时，返回第一个匹配的(最新）
        wbuf = lsv_info->wbuf;
        wb_hash_index = wbuf->wb_hash_index;
        list_add(&page->hook, &wb_hash_index[hash_key].list);

        return ret;
}

int lsv_wb_hash_index_lookup_v2(index_block_head *hash, lsv_u64_t offset, lsv_u32_t size, buffer_t *append_buf) {
        lsv_s32_t hash_key = 0;
        lsv_u64_t lba = 0;
        lsv_s32_t page_offset = 0;
        lsv_u32_t chunk_off = 0;
        lsv_chunk_buf_t *chunk_buf = NULL;
        struct list_head *pos;

        page_offset = offset % LSV_PAGE_SIZE;
        lba = OFF2LBA(offset);
        hash_key = HASH_KEY(lba);

        // DINFO("hash %u list size %d\n", hash_key, list_size(&hash[hash_key].list));

        list_for_each(pos, &hash[hash_key].list){
                // TODO 如果一个元素加入了list两次，会造成infinite loop
                // DINFO("pos %p\n", pos);
                chunk_page_t *page = (chunk_page_t *)pos;
                if(page->lba == lba){
                        chunk_buf = page->chunk->chunk_buf;
                        chunk_off = page->page_idx * LSV_PAGE_SIZE;

                        mbuffer_appendmem(append_buf,
                                          chunk_buf->page_buf + chunk_off + page_offset,
                                          size);
#ifdef __PAGE_CRC32__
                        uint32_t crc1 = chunk_buf->hlog_arg[page->page_idx + 1].hlog.crc;
#if WBUF_CHECK_CRC
                        uint32_t crc2 = page_crc32((void *)chunk_buf->page_buf + chunk_off);
                        YASSERT(crc1 == crc2);
#endif
#else
			uint32_t crc1 = 0;
#endif
                        DINFO("lba %llu crc %u chunk %p page %d hash %d\n",
                              (LLU)lba,
                              crc1,
                              page->chunk,
                              page->page_idx,
                              hash_key);
                        return size;
                }
        }

        return 0;
}

int lsv_wb_hash_index_gc_v2(lsv_wbuf_chunk_t *chunk){
        int ret = 0;
        lsv_chunk_buf_t *chunk_buf = chunk->chunk_buf;

        // YASSERT(chunk_buf->in_use == LSV_WBUF_CHUNK_POST_COMMIT);

        DINFO("clean chunk %d %p page %d in_use %u\n", chunk->id, chunk,
              chunk_buf->page_idx,
              chunk_buf->in_use);

        // 依次从全局hash里，删除所有记录
        for(int i = 0; i < chunk_buf->page_idx; i++) {
                chunk_page_t *page = &chunk->pages[i];
                list_del_init(&page->hook);
        }

        chunk_buf->in_use = LSV_WBUF_CHUNK_CLEAN;
        chunk_buf->page_idx = 0;
        chunk_buf->fill_count = 0;

        return ret;
}

int lsv_wb_hash_index_clean_all(lsv_volume_proto_t *lsv_info){
        lsv_wbuf_t *wbuf = lsv_info->wbuf;
        lsv_wbuf_chunk_t *chunk;
        struct list_head *pos, *next;

        DINFO("wait %u\n", wbuf->wait_list.count);
        list_for_each_safe(pos, next, &wbuf->wait_list.list){
                chunk = list_entry(pos, lsv_wbuf_chunk_t, hook);
                lsv_wb_hash_index_gc_v2(chunk);
        }

        DINFO("free %u\n", wbuf->free_list.count);
        list_for_each_safe(pos, next, &wbuf->free_list.list){
                chunk = list_entry(pos, lsv_wbuf_chunk_t, hook);
                lsv_wb_hash_index_gc_v2(chunk);
        }

        wbuf->last_chunk = NULL;
        wbuf->last_page_idx = -1;

        return 0;
}
